From ee0b7117321d850fa340f9102f224cc7b1d0948e Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 12 May 2016 15:12:16 -0700 Subject: [PATCH] Canonicalize CARGO_HOME fallback on Windows This commit ensures that we always return the same fallback value on Windows regardless of whichever shell we happen to be run from. We do this by removing the `$HOME` environment variable which `std::env::home_dir` will inspect to force it to fall back to the system APIs. If the old directory exists then we favor that one, but otherwise we favor locations like `C:\Users\$user` Supercedes and closes #2604 --- src/cargo/util/config.rs | 46 ++++++++++++++++++++++++++++++++++++++-- tests/tests.rs | 3 ++- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/cargo/util/config.rs b/src/cargo/util/config.rs index 9f41c56f5..d3ee857e3 100644 --- a/src/cargo/util/config.rs +++ b/src/cargo/util/config.rs @@ -600,8 +600,50 @@ fn homedir(cwd: &Path) -> Option { let cargo_home = env::var_os("CARGO_HOME").map(|home| { cwd.join(home) }); - let user_home = env::home_dir().map(|p| p.join(".cargo")); - cargo_home.or(user_home) + if cargo_home.is_some() { + return cargo_home + } + + // If `CARGO_HOME` wasn't defined then we want to fall back to + // `$HOME/.cargo`. Note that currently, however, the implementation of + // `env::home_dir()` uses the $HOME environment variable *on all platforms*. + // Platforms like Windows then have *another* fallback based on system APIs + // if this isn't set. + // + // Specifically on Windows this can lead to some weird behavior where if you + // invoke cargo inside an MSYS shell it'll have $HOME defined and it'll + // place output there by default. If, however, you run in another shell + // (like cmd.exe or powershell) it'll place output in + // `C:\Users\$user\.cargo` by default. + // + // This snippet is meant to handle this case to ensure that on Windows we + // always place output in the same location, regardless of the shell we were + // invoked from. We first check `env::home_dir()` without tampering the + // environment, and then afterwards we remove `$HOME` and call it again to + // see what happened. If they both returned success then on Windows we only + // return the first (with the $HOME in place) if it already exists. This + // should help existing installs of Cargo continue using the same cargo home + // directory. + let home_dir_with_env = env::home_dir().map(|p| p.join(".cargo")); + let home_dir = env::var_os("HOME"); + env::remove_var("HOME"); + let home_dir_without_env = env::home_dir().map(|p| p.join(".cargo")); + if let Some(home_dir) = home_dir { + env::set_var("HOME", home_dir); + } + + match (home_dir_with_env, home_dir_without_env) { + (None, None) => None, + (None, Some(p)) | + (Some(p), None) => Some(p), + (Some(a), Some(b)) => { + if cfg!(windows) && !a.exists() { + Some(b) + } else { + Some(a) + } + } + } } fn walk_tree(pwd: &Path, mut walk: F) -> CargoResult<()> diff --git a/tests/tests.rs b/tests/tests.rs index c41cb9b3c..ff330a5c5 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -92,8 +92,9 @@ fn is_nightly() -> bool { fn process>(t: T) -> cargo::util::ProcessBuilder { let mut p = cargo::util::process(t.as_ref()); p.cwd(&support::paths::root()) - .env("HOME", &support::paths::home()) .env_remove("CARGO_HOME") + .env("HOME", support::paths::home()) + .env("CARGO_HOME", support::paths::home().join(".cargo")) .env_remove("RUSTC") .env_remove("RUSTFLAGS") .env_remove("XDG_CONFIG_HOME") // see #2345 -- 2.30.2